package wolf.iu.framework;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map.Entry;

import wolf.iu.framework.deobs.Deob;
import wolf.iu.framework.hooks.AbstractClassHookEnum;
import wolf.iu.framework.hooks.AbstractFieldHookEnum;
import wolf.iu.framework.hooks.DependencyTransform;
import wolf.iu.framework.hooks.HookState;
import wolf.iu.framework.hooks.Transform;

public class Updater<T extends AbstractClassHookEnum, E extends AbstractFieldHookEnum> {

    ClassesHandler csc;
    HooksList hooks;

    ArrayList<Deob> deobs = new ArrayList<Deob>();
    ArrayList<Transform> transforms = new ArrayList<Transform>();
    String package_;

    T[] classEnums;
    E[] fieldEnums;

    public Updater(final ClassesHandler cc, final String package_,
            final T[] classEnums, final E[] fieldEnums) {
        this.csc = cc;
        this.package_ = package_;
        this.hooks = new HooksList(this.csc);

        this.classEnums = classEnums;
        this.fieldEnums = fieldEnums;
    }

    public void printLog() {
        final ArrayList<AbstractClassHookEnum> missingClassHooks = new ArrayList<AbstractClassHookEnum>();
        missingClassHooks.addAll(Arrays.asList(classEnums));

        final ArrayList<AbstractFieldHookEnum> missingFieldHooks = new ArrayList<AbstractFieldHookEnum>();
        missingFieldHooks.addAll(Arrays.asList(fieldEnums));
        
        int classHooks = 0;
        int fieldHooks = 0;

        for (final Entry<AbstractClassHookEnum, ClassHookContainer> entry : hooks
                .getClassMappings()) {
            missingClassHooks.remove(entry.getKey());
            classHooks++;

            String oldName = entry.getValue().getFullOldName();
            String implementing = entry.getKey().getName();
            System.out.println(oldName + " implements " + package_
                    + implementing);
            for (final Entry<AbstractFieldHookEnum, FieldHook> fentry : entry
                    .getValue().getFieldMappings()) {

                final AbstractFieldHookEnum fEnum = fentry.getKey();
                final FieldHook fHook = fentry.getValue();
                
                missingFieldHooks.remove(fEnum);
                fieldHooks++;
                
                oldName = fHook.getOldName();
                
                if (fHook.isStatic()) {
                    oldName = fHook.getOwner() + "." + oldName;
                }
                
                implementing = fEnum.getName();
                System.out.println(" -> " + (fHook.isStatic() ? "static " : "") + "[" + fHook.getDesc() + "]" + oldName + " => "
                        + implementing);
            }
        }
        
        System.out.println();
        
        System.out.println("Found class hooks: " + classHooks);
        System.out.println("Found field hooks: " + fieldHooks);

        System.out.println("Missing class hooks: " + missingClassHooks.size());
        for (final AbstractClassHookEnum cHook : missingClassHooks) {
            System.out.println(" - " + cHook.getName());
            for (final AbstractFieldHookEnum fHook : cHook.fieldHooks()) {
                
                missingFieldHooks.remove(fHook);
                
                System.out.println("     -> " + cHook.getName() + "." + fHook.getName());
            }
        }
        
        System.out.println("Missing field hooks: " + missingFieldHooks.size());
        for (final AbstractFieldHookEnum cHook : missingFieldHooks) {
            System.out.println(" - " + cHook.getName());
        }
        
    }

    public void run() {
        
        System.out.println();
        
        final ArrayList<ClassContainer> ccs = new ArrayList<ClassContainer>();
        ccs.addAll(csc.getClasses());
        
        if (deobs.size() > 0) {
            
            System.out.println(" Starting deob with " + deobs.size() + " deobs");
            final Timer deobTimer = new Timer();
            
            for (final Deob deob : deobs) {
                
                final Timer sDeobTimer = new Timer();
                System.out.println("  Running " + deob.getClass().getSimpleName() + " for " + ccs.size() + " classes");
                
                for (final ClassContainer cc : ccs) {
                    deob.deob(cc.getNode());
                }
                
                System.out.println("  " + deob.getClass().getSimpleName() + " done in " + sDeobTimer.stop() + "ms");
            }
            
            System.out.println(" Deobbing done in " + deobTimer.stop() + "ms");
            
            System.out.println();
            
        }
        
        if (transforms.size() > 0) {
            
            final Timer hUpdTimer = new Timer();
            
            System.out.println(" Starting hook updater");
            
            final ArrayList<Transform> found = new ArrayList<Transform>();

            final ArrayList<Transform> secondRound = new ArrayList<Transform>();
            
            final ArrayList<Transform> toCheck = new ArrayList<Transform>();
            toCheck.addAll(transforms);

            while (toCheck.size() > 0) {
                final Transform transform = toCheck.remove(0);
                if (found.contains(transform)) {
                    continue;
                }
                if (transform instanceof DependencyTransform) {
                    final AbstractClassHookEnum dependencies = ((DependencyTransform) transform)
                            .dependency();
                    if (!hooks.containsHook(dependencies)) {
                        if (!secondRound.contains(transform)) {
                            toCheck.add(transform);
                            secondRound.add(transform);
                        }
                        continue;
                    }
                }

                for (final ClassContainer cc : ccs) {

                    final HookState state = transform.execute(csc, cc, hooks);

                    if (state == HookState.Found) {
                        System.out.println("  " + transform.getClass().getSimpleName() + " == HookState.Found");
                        found.add(transform);
                        break;
                    }
                }

            }
            
            System.out.println(" Hook updater finished in " + hUpdTimer.stop() + "ms");
            
            System.out.println();
            
        }
        
        System.out.println();
        
    }

    public void addTransform(final Transform transform) {
        this.transforms.add(transform);
    }

    public void addTransforms(final Collection<Transform> transforms) {
        this.transforms.addAll(transforms);
    }
    
    public void addDeob(final Deob deob) {
        this.deobs.add(deob);
    }

}
